package gu.sql2java.manager;

import static com.google.common.base.Preconditions.checkNotNull;
import static gu.sql2java.SimpleLog.log;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import com.google.common.base.Function;

import gu.sql2java.BaseBean;
import gu.sql2java.TableManager;
import gu.sql2java.exception.QueueTimeoutException;

/**
 * [线程安全]实现向阻塞队列输出数据库记录的{@link gu.sql2java.TableManager.Action}接口<br>
 * 为异步输出数据库查询记录提供技术基础，
 * 作为数据库记录的生产者向将从数据库中读取的数据库记录转换为指定的类型添加到阻塞队列，供另一端的消费者使用，
 * 当消费端停止消费时，即停止工作。
 * @author guyadong
 *
 * @param <B> 从数据库读取的原始记录对象类型
 * @param <T> 队列中保存的基于B转换的数据库对象类型
 * @since 3.15.4
 */
public class QueueProducerAction<B extends BaseBean,T> implements TableManager.Action<B> {
	/**
	 * 默认队列容量
	 */
	static final int DEFAULT_QUEUE_CAPACITY = 1000;
	/**
	 * 默认队列插入超时时间(秒)
	 */
	static final int DEFAULT_QUEUE_TIMEOUT = 10;
	/**
	 * 保存数据库记录的阻塞队列
	 */
	private final BlockingQueue<T> queue;
	/**
	 * 数据类型转换器
	 */
	private final Function<B, T>transformer;
	/**
	 * 插入队列超时时间(秒)
	 */
	private final int queueTimeout;
	/**
	 * 数据库记录计数器
	 */
	private final AtomicLong count = new AtomicLong(0);
	/**
	 * 构造方法<br>
	 * @param queue 阻塞队列
	 * @param queueTimeout 插入队列超时时间(秒),
	 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入,
	 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
	 * @param transformer B到T的数据类型转换器
	 */
	public QueueProducerAction(BlockingQueue<T> queue, int queueTimeout,Function<B, T> transformer) {
		this.queue = checkNotNull(queue,"queue is null");
		this.queueTimeout = queueTimeout > 0 ? queueTimeout : DEFAULT_QUEUE_TIMEOUT;
		this.transformer = checkNotNull(transformer,"transformer is null");
	}
	/**
	 * 构造方法<br>
	 * @param queueCapaticy 队列容量,小于等于0使用默认值{@link #DEFAULT_QUEUE_CAPACITY}
	 * @param queueTimeout 插入队列超时时间(秒),
	 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入,
	 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
	 * @param transformer B到T的数据类型转换器
	 */
	public QueueProducerAction(int queueCapaticy, int queueTimeout,Function<B, T> transformer) {
		this.queue = new LinkedBlockingQueue<T>(queueCapaticy > 0 ? queueCapaticy : DEFAULT_QUEUE_CAPACITY);
		this.queueTimeout = queueTimeout > 0 ? queueTimeout : DEFAULT_QUEUE_TIMEOUT;
		this.transformer = checkNotNull(transformer,"transformer is null");
	}
	/**
	 * 简化版本构造方法<br>
	 * 队列容量和插入队列超时时间使用默认值
	 * @param transformer B到T的数据类型转换器
	 */
	public QueueProducerAction(Function<B, T> transformer){
		this(0, 0, transformer);
	}
	public BlockingQueue<T> getQueue() {
        return queue;
    }
    /**
	 * 将数据库中获取的原始数据库对象通过转换器({@link #transformer})转换为指定类型插入到阻塞队列中。
	 * 当阻塞队列满添加元素超时即视为消费端停止，即停止工作，抛出{@link QueueTimeoutException}异常，
	 * 以中止从数据库中读取记录的过程
	 */
	@Override
	public void call(B bean) {
        try {
            if(!queue.offer(transformer.apply(bean), queueTimeout, TimeUnit.SECONDS)){
                /** 队列添加元素超时，判断为消费端停止，即停止工作 */
                throw new QueueTimeoutException();
            }
            long c = count.incrementAndGet();
            if(0 == c % 10000){
                log(BaseTableManager.isDebug(),"FETCH {} rows",c);
            }
        } catch (InterruptedException e) {
            throw new QueueTimeoutException(e);
        }
	}
	/**
	 * {@link QueueProducerAction}的简化版本,队列类型为数据库原始记录类型
	 * @author guyadong
	 *
	 * @param <B> 从数据库读取的原始记录对象类型
	 * @since 3.15.4
	 */
	public static class SimpleQueueProducerAction<B extends BaseBean> extends QueueProducerAction<B,B >{
		/**
		 * 构造方法<br>
		 * @param queue 阻塞队列
		 * @param queueTimeout 插入队列超时时间(秒),
		 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入,
		 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
		 */
		public SimpleQueueProducerAction(BlockingQueue<B> queue, int queueTimeout) {
			super(queue, queueTimeout, b->b);
		}
		/**
		 * 构造方法<br>
		 * @param queueCapaticy 队列容量,小于等于0使用默认值{@link #DEFAULT_QUEUE_CAPACITY}
		 * @param queueTimeout 插入队列超时时间(秒),
		 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入,
		 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
		 */
		public SimpleQueueProducerAction(int queueCapaticy, int queueTimeout) {
			super(queueCapaticy, queueTimeout, b->b);
		}
		/**
		 * 默认构造方法<br>
		 * 队列容量和插入队列超时时间使用默认值
		 */
		public SimpleQueueProducerAction() {
			super(b->b);
		}
		
	}
}